/* eslint-disable dot-notation */
interface FramesData {
	res: string;
	x: number;
	y: number;
	duration?: number;
}

interface AniEvent {
	name: string;
	frame: number;
	index?: number; // 这是自己的格式（非白鹭标准），用index比frame容易设置！
}

function isAniEventHit(ani: AniEvent) {
	return ani.name === '@hit';
}

interface MCData {
	ani?: string;
	frameRate: number;
	frames: FramesData[];
	events: AniEvent[];
	// labels: any[];
}

interface ResData {
	x: number;
	y: number;
	w: number;
	h: number;
	offx: number;
	offy: number;
}

const Chinese2McKey = {
	遮罩: 'building',
	影子: 'shadow',
	灯光: 'light',
	阻挡: 'obs',
	上层阻挡: 'uobs',
	光阻挡: 'lobs',
	平台: 'plateform',
};

interface McDataEx {
	building?: number[]; // 2个点
	shadow?: number[];
	light?: any[];
	obs?: number[];
	uobs?: number[];
	lobs?: number[];
	plateform?: number[];
}

// 1. offsetKey 来设置MCFile的offx ,offy
// 2. 重载了 MovieClipDataFactoryEx.generateMovieClipData 把MCFile.offx,offy等设置到mcDataSet
// 3. 重载了 MovieClipData.$getOffsetByFrame 用上了offx,offy

interface MCFile extends McDataEx {
	mc: { [key: string]: MCData };
	res: { [key: string]: ResData };
	offx?: number;
	offy?: number;
}

// 自动填充如果有hit事件，自动填充attack事件在hit前面3帧
// 需要工具去掉手动的attack事件
function autoAttackBeforeHitMC(shape: number, key: string, v: MCData) {
	if (!v.events) {
		return;
	}

	let len = v.events.length;
	for (let i = 0; i < len; i++) {
		const e = v.events[i];
		if (e.name === '@attack') {
			return;
		}
	}

	len = v.events.length;
	for (let i = 0; i < len; i++) {
		const e = v.events[i];
		if (e.name === '@hit') {
			const newe = {
				name: '@attack',
				frame: 0,
				index: e.index - 3 > 0 ? e.index - 3 : 0,
			};
			newe.frame = getStartFrame(v.frames, newe.index);
			v.events.push(newe);
			// console.log('autoAttackBeforeHitMC', shape, key, newe.index, newe.frame);
		}
	}
}

function autoAttackBeforeHit(shape: number, mc: MCFile) {
	for (const [k, v] of Object.entries(mc.mc)) {
		if (k.includes('attack')) {
			autoAttackBeforeHitMC(shape, k, v);
		}
	}
}

const extendAnis: Record<string, string> = {
	relife: 'die',
	jumpup: 'jump',
	jumpfly: 'jump',
	jumpdown: 'jump',
	hand: 'exchange',
};

function cloneMCFile(data: MCFile): MCFile {
	return {
		offx: data.offx,
		offy: data.offy,
		res: data.res,
		mc: {},
	};
}

function reverseAnieData(data2: MCData): MCData {
	data2.frames.reverse();
	return data2;
}

// 简单的转换，relife直接反向die, jumpattack取attack的后半段
function die2RelifeData(data2: MCData): MCData {
	return reverseAnieData(data2);
}

function attack2JumpattackData(data2: MCData): MCData {
	const len = data2.frames.length;
	data2.frames.splice(0, len / 2);
	return data2;
}

function magic2HalfData(data2: MCData): MCData {
	const len = data2.frames.length;
	data2.frames.splice(len / 2, (len + 1) / 2);
	return data2;
}

function aniToExtend(ani: string, src: string, data: MCFile): MCFile {
	const data2 = cloneMCFile(data);
	const datasrc: MCData = Utils.deepCopy(data.mc[src]);
	if (ani === 'relife' && src === 'die') {
		die2RelifeData(datasrc);
	}
	// if (ani === 'jumpattack' && src === 'attack') {
	// 	attack2JumpattackData(datasrc);
	// }
	// if (ani === 'magichalf' && src === 'magic') {
	// 	magic2HalfData(datasrc);
	// }
	if (ani === 'hand' && src === 'exchange') {
		datasrc.frames.splice(6);
	}
	if (ani === 'jumpup' && src === 'jump') {
		datasrc.frames.splice(6);
	}
	if (ani === 'jumpdown' && src === 'jump') {
		datasrc.frames.splice(0, 6);
	}
	if (ani === 'jumpfly' && src === 'jump') {
		datasrc.frames = [datasrc.frames[6]];
	}

	data2.mc[ani] = datasrc;
	data2.mc[ani].ani = src;
	// return Utils.clone(data);
	return data2;
}

class CharCache extends JsonCache {
	private static instance: CharCache;

	public static ins(): CharCache {
		if (!this.instance) {
			this.instance = new CharCache();
		}
		return this.instance;
	}

	override isserver = false;

	override getFull(num: string): string {
		return getCharFile(Number(num));
	}

	override setCache(name: string, json: MCFile): void {
		eventIndex2start(json.mc);
		this.cache[name] = json;
	}
}

// FrameData like:
// 0: {res: "1", duration: 2, x: -49, y: -162}
// 1: {frame: 3}
// 2: {res: "2", duration: 2, x: -65, y: -159}
// 3: {frame: 5}

function getStartFrame(frames: FramesData[], index: number): number {
	let nframe = 1; // movieclip start at 1
	let i = 0;
	for (const f of frames) {
		if (i === index) {
			return nframe;
		}
		if (f.duration) {
			nframe += f.duration;
			i++;
		}
	}
	return 0;
}

function getResFrame(frames: FramesData[], res: string): number {
	let nframe = 1; // movieclip start at 1
	for (const f of frames) {
		if (f.res === res) {
			return nframe;
		}
		nframe += f.duration || 1;
	}
	return 0;
}

// egret的事件是由绝对的帧数发生的，而我们常常习惯于设定于第几帧
function eventIndex2start(mcs: { [key: string]: MCData }): void {
	for (const _mc of Object.values(mcs)) {
		// const mc = mcs[key];
		const mc = _mc;
		if (mc.events) {
			for (const e of mc.events) {
				if (e.index != null) {
					const start = getStartFrame(mc.frames, e.index);
					e.frame = start;
				}
			}
		}
	}
}

class MovieClipDataEx extends egret.MovieClipData {
	mcFile: MCFile;

	constructor(mcFile: MCFile) {
		super();
		this.mcFile = mcFile;
	}

	override $getOffsetByFrame(frame: number, point: egret.Point): void {
		const frameData = this.getKeyFrameData(frame);
		if (frameData.res) {
			const offx = this.mcFile.offx || 0;
			const offy = this.mcFile.offy || 0;
			point.setTo((frameData.x || 0) + offx, (frameData.y || 0) + offy);
		}
	}
}

// 1 offset都放在res中
// 2 可以主动添加action
class MovieClipDataFactoryEx extends egret.MovieClipDataFactory {
	constructor(movieClipDataSet: MCFile, texture: egret.Texture) {
		assert(texture != null);
		super(movieClipDataSet, texture);
		this.mcFile = movieClipDataSet;
		this.offsetToRes();
		eventIndex2start(this.$mcDataSet.mc);
	}

	mcFile: MCFile;

	// 把offset从frames放到res
	offsetToRes(): void {
		const ani0 = this.$mcDataSet.mc['0'];
		if (ani0) {
			for (const f of ani0.frames) {
				const { res } = f;
				const resdata: ResData = this.$mcDataSet.res[res];
				resdata.offx = f.x;
				resdata.offy = f.y;
			}
		}
	}

	cloneAction(ani: string, src: string): void {
		if (!this.$mcDataSet.mc[ani]) {
			this.$mcDataSet.mc[ani] = this.$mcDataSet.mc[src];
			this.$mcDataSet.mc[ani].ani = ani;
		}
	}

	addAction(ani: string, data2: MCData): void {
		// if (!this.$mcDataSet.mc[ani]) {
		const data = Utils.deepCopy(data2);
		this.$mcDataSet.mc[ani] = data;
		// updateoffset for ani
		for (const f of data.frames) {
			const { res } = f;
			const resdata: ResData = this.$mcDataSet.res[res];
			f.x = resdata.offx;
			f.y = resdata.offy;
		}
		// }
	}

	override createMovieClipData(): MovieClipDataEx {
		return new MovieClipDataEx(this.mcFile);
	}

	public override generateMovieClipData(movieClipName = '0'): MovieClipDataEx {
		const data = super.generateMovieClipData(movieClipName) as MovieClipDataEx;
		assert(data != null);
		if (data.frames.length === 0) {
			assert(false);
		}
		// 这里把全局的信息赋给每一个动画！
		if (data) {
			data.mcFile = this.mcFile;
			// const keys = ['offx', 'offy', ...Object.values(Chinese2McKey)];
			// for (const key of keys) {
			// 	data[key] = this.$mcDataSet[key];
			// }
			// if (this.$mcDataSet.building){
			// console.log("generateMovieClipData buiding",this.$mcDataSet.building)
			// }
		}
		return data;
	}

	resetData(): void {
		// 看不懂这是啥意思了！只保留0？
		const old = this.$mcDataSet.mc;
		this.$mcDataSet.mc = {
			0: old['0'],
		};
		// this.$mcDataSet.
	}

	// shape,ani,dir只是为了调试方便

	shape: number;

	ani: string;

	dir: number;

	setAniDir(shape: number, ani: string, dir: number): void {
		this.shape = shape;
		this.ani = ani;
		this.dir = dir;
	}
}

class CharMCFactory {
	shape: number;

	data: MCFile;

	anis: { [key: string]: MovieClipDataFactoryEx } = {};

	resetData(data: MCFile): void {
		CharCache.ins().setCache(String(this.shape), data);
		this.data = data;
		for (const f of Object.values(this.anis)) {
			f.resetData();
		}
		this.anis = {};
	}

	async createShape(shape: number): Promise<boolean> {
		// 首先从charani中读取配置，如果读不到就从目录创建
		let data: MCFile = await CharCache.ins().loadCache(String(shape));
		if (!data) {
			ui.msglog('createShape error', shape);
			data = newAni(shape, true); // if not editor can not newAni
			CharCache.ins().setCache(String(shape), data);
		}
		autoAttackBeforeHit(shape, data);
		this.shape = shape;
		this.data = data;
		egret.assert(data != null);
		return true;
	}

	maxDir(ani: string): number {
		const anidata = this.data ? this.data.mc[ani] : null;
		let anibase = ani;
		if (anidata && anidata.ani) {
			anibase = anidata.ani;
		}
		if (
			anibase === 'walk' ||
			anibase === 'stand' ||
			anibase === 'rwalk' ||
			anibase === 'rstand'
		) {
			return 8;
		}
		return 4;
	}

	async createMC(ani: string, dir: number): Promise<MovieClipDataEx> {
		const anidata = this.data ? this.data.mc[ani] : null;
		// anidata包括编辑的event

		let anibase = ani;
		if (anidata && anidata.ani) {
			anibase = anidata.ani;
		}

		const key = ani + dir;
		let factory = this.anis[key];
		// let gen = ani
		if (!factory) {
			if (!anidata) {
				factory = await this.getBaseOrExtend(ani, dir);
			} else {
				factory = await this.getBase(anibase, dir);
				if (!factory) {
					return null;
				}
				// 用charani下的数据覆盖掉ani
				factory.addAction(ani, anidata);
				if (anibase !== ani) {
					if (this.data.mc[anibase]) {
						factory.addAction(anibase, this.data.mc[anibase]);
					}
				}
			}
		}
		if (factory) {
			return factory.generateMovieClipData(ani);
		}
		return null;
	}

	bodyStr(): string {
		return miscUtils.stringPad(this.shape, 4);
	}

	async getBase(ani: string, dir: number, warn = true): Promise<MovieClipDataFactoryEx> {
		const key = ani + dir;
		let factory = this.anis[key];
		if (!factory) {
			factory = await this.createMCFRaw(ani, dir, warn);
		}
		return factory;
	}

	async getBaseOrExtend(ani: string, dir: number): Promise<MovieClipDataFactoryEx> {
		const base = await this.getBase(ani, dir, false);
		if (base) {
			return base;
		}
		return this.getExtend(ani, dir);
	}

	async getExtend(ani: string, dir: number): Promise<MovieClipDataFactoryEx> {
		const src = extendAnis[ani];
		if (!src) {
			return null;
		}
		const f: MovieClipDataFactoryEx = await this.getBase(src, dir, false);
		if (!f) return null;
		const data = aniToExtend(ani, src, f.mcDataSet);
		if (!data) return null;
		const f2 = new MovieClipDataFactoryEx(data, f.spriteSheet.$texture);
		assert(f2.spriteSheet != null, `getExtend f2.spriteSheet != null ${src}=>${ani}`);
		this.addFactory(ani, dir, f2);
		return f2;
	}

	addFactory(ani: string, dir: number, f2: MovieClipDataFactoryEx): void {
		f2.setAniDir(this.shape, ani, dir);
		this.anis[ani + dir] = f2;
	}

	async createMCFRaw(ani: string, dir: number, warn: boolean): Promise<MovieClipDataFactoryEx> {
		const file = `${charRoot}/${this.bodyStr()}/${ani}_${dir}`;
		const f = await loadMcDataFactoryByUrl(file);
		if (f) {
			f.cloneAction(ani, '0');
			this.addFactory(ani, dir, f);
		} else if (warn) {
			console.error('createMCRaw error!', { shape: this.shape, ani, dir });
		}
		return f;
	}

	getStartFrame(ani: string, index: number): number {
		if (this.data) {
			const anidata = this.data.mc[ani];
			if (anidata) {
				return getStartFrame(anidata.frames, index);
			}
		}
		return -1;
	}
}

class CharMCFactorys {
	chars: { [key: number]: CharMCFactory } = {};

	private static instance: CharMCFactorys;

	public static ins(): CharMCFactorys {
		if (!this.instance) {
			this.instance = new CharMCFactorys();
		}
		return this.instance;
	}

	async getCharFactory(shape: number): Promise<CharMCFactory> {
		let char = this.chars[shape];
		if (char) {
			return char;
		}
		char = new CharMCFactory();
		await char.createShape(shape);
		this.chars[shape] = char;
		return char;
	}

	getStartFrame(shape: number, ani: string, index: number): number {
		const f = this.chars[shape];
		if (f) {
			return f.getStartFrame(ani, index);
		}
		return -1;
	}
}

async function createCharMC(shape: number, ani: string, dir: number): Promise<WestMCBody> {
	// return guardFunc2('createCharMC', async () => {
	const f = await CharMCFactorys.ins().getCharFactory(shape);
	const mcdata = await f.createMC(ani, dir);
	if (!mcdata) {
		return null;
	}
	// return new westMCBody(mcdata,"");
	const body = Pool.ins().pop('CharFactory', 'westMCBody', WestMCBody, mcdata, '');
	return body;
	// });
}
